Vorwort
Das hier gezeigte Vorgehen, um Objekte zu speichern, ist sicher nicht das Optimum. Aber es ist ein guter Startpunkt für
weitere Entwicklungen. Da ich kein großer Redner bin, gehts aber auch gleich los
Vorarbeiten
Zuerstmal müssen wir gleich eine Hürde umgehen. Nämlich die, das die beiden Routinen ReadProperty und WriterProperties
von TReader bzw. TWriter protected sind. Damit haben wir erstmal keine Chance diese Routinen zu nutzen um
unsere Objekte (bzw. deren Eigenschaften) zu speichern.
Aber Delphi wäre nicht Delphi, und
OOP nicht
OOP, wenn es dafür keine Lösung gäbe. Wir leiten uns einfach eine eigene
TReader und TWriter-Klasse ab
Delphi-Quellcode:
TYPE
TReaderEx = class(TReader)
PUBLIC
procedure LoadProperty(Instance:TPersistent); //Anderer Name um Konflikte zu vermeiden.
end.
TWriterEx = class(TWriter)
PUBLIC
Procedure SaveProperties(Instance:TPersistent); //Anderer Name um Konflikte zu vermeiden.
end.
Procedure TReaderEx.LoadProperty(Instance:TPersistent);
begin
ReadProperty(instance);
end;
Procedure TWriterEx.SaveProperties(Instance:TPersisten);
begin
WriteProperties(Instance);
end;
So..das wars schon................oder doch nicht ?
Nein...nicht ganz. Es würden zwar alle Eigenschaften geschrieben, aber nur eine gelesen werden (kann man ganz
gut an der Namensgebung der orginal Prozeduren erkennen). Tja..und wie lösen wir das Problemchen ?
Nun..einfach mit einem Listchen und einem Schleifchen.
Schließlich braucht der Streamingmechanismus eine
Kennung, wo die Eigenschaften eines Objekts aufhören und die des nächsten anfangen. Ein Blick in die Sourcen der
VCL
gibt die Lösung Preis (was Codegear fabriziert, funktioniert ja auch
).
Delphi-Quellcode:
Procedure TReaderEx.LoadProperty(Instance:TPersistent);
begin
While not EndOfList do ReadProperty(instance);
ReadListEnd;
end;
Procedure TWriterEx.SaveProperties(Instance:TPersisten);
begin
WriteProperties(Instance);
WriteListEnd;
end;
Das wars schon.
Die Funktionen
Da TStream und andere abeleitete Klassen nichts von unseren Erweiterungen wissen, müssen wir einen kleinen Umweg wählen, um schlußendlich die Objekte zu speichern. Also bauen wir uns fix zwei Funktionen die uns die gewünschten Objekte in einen Stream schreiben.
Delphi-Quellcode:
function WritePersistentToStream(Astream:TStream;AObject:TPersistent):boolean;
function ReadPersistentFromStream(AStream:TStream;AObject:TPersistent):boolean;
Implementation
function WritePersistentToStream(Astream:TStream;AObject:TPersistent):boolean;
var
writer : TWriterEx;
begin
result := FALSE;
writer :=
NIL;
try
writer := TWriterEx.Create(AStream,1024);
Writer.SaveProperties(AObject);
writer.FlushBuffer;
freeandnil(writer);
result := TRUE;
except
on e:
exception do
begin
if (writer <>
NIL)
then
FreeAndNil(Writer);
end;
end;
end;
function ReadPersistentFromStream(AStream:TStream;AObject:TPersistent):boolean;
var
reader : TReaderEx;
begin
reader :=
NIL;
result := FALSE;
try
reader := TReaderEx.Create(AStream,1024);
reader.LoadProperty(AObject);
FreeAndNil(Reader);
result := TRUE;
except
on e:
exception do
begin
if (reader <>
NIL)
then
FreeAndNil(reader);
end;
end;
end;
Zuerst erzeugen wir uns also eine Instanz von unserer Reader/Writer-Klasse, und verknüpfen diese mit dem gewünschten
Stream. Dann Speichern/Lesen wir die Eigenschaften unseres Objektes. Unsere Instanzen geben wir natürlich wieder frei.
Den Rest erledigt der
VCL-Streaming-Mechanismus mit Hilfe der
RTTI
Das Prunkstück (unser Objekt)
Um das ganze nun auch mal in Aktion zu bekommen, brauchen wir natürlich auch noch ein Objekt, das wir
speichern bzw. laden wollen. Damit das ganze auch funktioniert, müssen wir unser Objekt (bzw. die Objektklasse) von
TPersistent ableiten (bereits abgeleitete Klassen von TPersistent sollten auch als Basis funktionieren).
Delphi-Quellcode:
TMeinPrunktstueck = class(TPersistent)
PRIVATE
fStr : String;
fInt : Integer;
fid : string;
farr : array[0..9] of String;
function GetItem(index: integer): string;
procedure SetItem(index: integer; const Value: string);
PROTECTED
Procedure ReadItems(Reader:TReader);
Procedure WriteItems(Writer:TWriter);
PUBLIC
property Items[index:integer]:string read GetItem write SetItem;
Constructor Create;
Destructor Destroy;override;
Procedure Assign(Source:TPersistent);override;
Procedure DefineProperties(Filer:TFiler);override;
PUBLISHED
property Astring:String read fstr write fstr;
property AInteger:Integer read fint write fint;
property ID:string read fid write fid;
end;
Isse nicht schön..die Klasse
Nun...standardmäßig werden von der
VCL ja nur Published-Eigenschaften geschrieben und gelesen. Manche Eigenschaften
können wir aber nicht als Published deklarieren (wie hier ein Array). Aber auch dafür gibts eine Lösung. Mit Hilfe der
DefineProperties-Methode sowie ReadItems und Writeitems, speichern wir auch unser Array ab. Theoretisch ließe sich
auch irgendwas anderes damit im Stream speichern. Aber hier will ich erstmal das Prinzip verdeutlichen.
Mit Hilfe von DefineProperties kann man sog. Pseudo-Properties in das Streamingsystem einfügen. Sogar die
VCL macht davon
gebrauch (So merkt sich Delphi die Position von nicht-visuellen Komponenten
)
Delphi-Quellcode:
Procedure TMeinPrunktstueck.DefineProperties(Filer:TFiler);
begin
Filer.DefineProperty('ITEMS',ReadItems,WriteItems,true);
end;
Damit sagen wir dem System, das unsere Klasse eine Pseudo-Eigenschaft "ITEMS" hat, die mit Hilfe der beiden
Methoden ReadItems und Writeitems gelesen bzw. geschrieben werden können. Außerdem sagen wir dem System,
das immer Daten für diese Eigenschaft vorhanden sind.
Nun noch schnell die Methoden zum Lesen und Schreiben implementiert:
Delphi-Quellcode:
procedure TMeinPrunkstueck.ReadItems(Reader: TReader);
var
I : integer;
begin
reader.ReadListBegin;
for I := 0 to 9 do
farr[i] := reader.ReadString;
reader.ReadListEnd;
end;
procedure TMeinPrunkstueck.WriteItems(Writer: TWriter);
var
I : integer;
begin
writer.WriteListBegin;
for I := 0 to 9 do
writer.WriteString(farr[i]);
writer.WriteListEnd;
end;
fertig.
Das Muster
Ab jetzt können wir mit unseren beiden Funktionen ReadPersistentFromStream und WritePersistentToStream unser Prunkstück
lesen und schreiben. Und zwar egal in was, solange es von TStream abgeleitet ist. Ob das ganze eine Datei, ein Archiv
oder gar eine Netzwerkverbindung ist, ist dem System egal. Hauptsache TStream.
Delphi-Quellcode:
procedure TForm21.LoadObjectFromStream;
begin
if (Prunkstueck1 = NIL) then
Prunkstueck1 := TMeinPrunkstueck.create;
ReadPersistentFromStream(AFilestream,Prunkstueck1);
end;
Procedure TForm21.SaveObjectToStream;
begin
if (Prunktstueck1 <> NIL) then
writepersistenttosteam(AFilestream,Prunkstueck1);
end;
WICHTIGER HINWEIS
Wenn ihr mehrer Objekte in einem Stream speichern bzw. lesen wollt, müsst ihr auf die Reihenfolge achten, in der
die einzelnen Objekte gelesen und gespeichert werden !
Schlußwort
Die beschrieben Technik ist sicher noch nicht das Optimum. Deshalb freu ich mich über Anregungen und Hinweise
Ein komplettes Beispiel ist in Vorbereitung und wird demnächst hier angehangen.